#!/usr/bin/env python

"""This module is used to parse the Google Picasa Web API feeds"""

import urllib2
import datetime, time
import stat
import xml.dom.minidom as minidom
import errno
from fuse import Fuse

class Object:
    title = ''
    summary = ''
    last_updated = None
    size = 0

    def get_title (self, xml_node):
        title = xml_node.getElementsByTagName('title')
        if title and title[0].childNodes:
            return title[0].childNodes[0].nodeValue
        # raise exception here
        return ''

    def get_time(self):
        """Converts datetime object into time from epoch"""
        upd = self.last_updated
        if not upd:
            return 0
        return int(time.mktime((upd.year, upd.month, upd.day, upd.hour,
                                upd.minute, upd.second, 0, 0, 0)))

    def find_size(self):
        return self.size
    
    def get_summary (self, xml_node):
        summary = xml_node.getElementsByTagName('summary')
        if summary and summary[0].childNodes:
            return summary[0].childNodes[0].nodeValue
        return ''

    def get_last_updated (self, xml_node):
        updated = xml_node.getElementsByTagName('updated')[0].childNodes[0].nodeValue
        date = updated[:10].split('-')
        time = updated[11:19].split(':')
        return datetime.datetime (*map(int, (date[0], date[1], date[2],
                                             time[0], time[1], time[2])))
        
    def set_values (self, xml_node):
        self.last_updated = self.get_last_updated(xml_node)
        self.title = self.get_title(xml_node)
        self.summary = self.get_summary(xml_node)

    def get_value(self, xml_node, value):
        val = xml_node.getElementsByTagName(value)
        if val and val[0].childNodes:
            return val[0].childNodes[0].nodeValue
        return None
    
class Photo(Object):
    def __init__ (self, xml_node):
        self.image = None
        self.set_values(xml_node)
        self.width = self.get_width(xml_node)
        self.height = self.get_height(xml_node)
        self.size = self.get_size(xml_node)
        self.url = self.get_url(xml_node)
        print self.url, self.size, self.height, self.width, self.title

    def get_width(self, xml_node):
        width = self.get_value(xml_node, 'gphoto:width')
        if width: return long(width)
        return 0

    def get_height(self, xml_node):
        height = self.get_value(xml_node, 'gphoto:height')
        if height: return long(height)
        return 0

    def get_size(self, xml_node):
        size = self.get_value(xml_node, 'gphoto:size')
        if size: return long(size)
        return 0

    def get_url(self, xml_node):
        content = xml_node.getElementsByTagName('content')[0]
        return content.getAttribute('src')
    
    def download_image(self):
        http_handler = urllib2.HTTPHandler()
        req = urllib2.Request(self.url)
        self.image = http_handler.http_open(req).read()

    def read(self, length, offset):
        if not self.image:
            self.download_image()
        return self.image[offset:offset+length]
        
class Album(Object):
    url = 'http://picasaweb.google.com/data/feed/api/user/'
    def __init__ (self, xml_node, username):
        self.keywords = []
        self.list_of_photos = []
        self.set_values(xml_node)
        self.rights = self.get_rights(xml_node)
        self.numphotos = self.get_numphotos(xml_node)
        self.size = self.numphotos
        self.owner = username
        self.http_handler = urllib2.HTTPHandler()

    def find_photo(self, photoname):
        for photo in self.list_of_photos:
            if photo.title == photoname:
                return photo

    def get_rights(self, xml_node):
        rights = self.get_value(xml_node, 'rights')
        if rights: return rights
        return 'public'

    def get_numphotos(self, xml_node):
        num = self.get_value(xml_node, 'numphotos')
        if num: return long(num)
        return 0

    def get_photos(self):
        """Only add/remove files. Do not update files which have been
        downloaded. TO BE IMPLEMENTED"""
        if not self.list_of_photos: self.populate_album()
        return [photo.title for photo in self.list_of_photos]
        
    def populate_album(self):
        """Populate the album with photos"""
        url = self.url + self.owner + '/album/' + self.title + '?kind=photo'
        req = urllib2.Request (url)
        res = self.http_handler.http_open(req).read()
        xml_file = minidom.parseString(res)
        for entry in xml_file.getElementsByTagName('entry'):
            self.list_of_photos.append(Photo(entry))
            
        
class Users(Object):
    url = 'http://picasaweb.google.com/data/feed/api/user/'
    def __init__(self, username=''):
        self.subtitle = ''
        self.total_results = 0
        self.itemsPerPage = 0
        self.http_handler = urllib2.HTTPHandler()
        self.albums = []
        self.title = username
        self.is_authenticated = False
        self.expanded = False
        
    def login(self, username, password):
        """do login stuff here"""
        # Check the page at http://code.google.com/apis/accounts/Authentication.html
        pass

    def destroy_values(self):
        for album in self.albums:
            del album
        self.albums = []
        self.size = 0
        
    def get_albums(self):
        self.destroy_values()
        self.populate_user()
        return [album.title for album in self.albums]
        
    def find_album(self, albumname):
        if not self.expanded:
            self.populate_user()
        for album in self.albums:
            if album.title == albumname:
                return album
            
    def get_total_results(self, xml_node):
        res = self.get_value(xml_node, 'openSearch:totalResults')
        if res: return long(res)
        return 0

    def populate_user(self):
        url = self.url + self.title + '?kind=album'
        req = urllib2.Request(url)
        response = self.http_handler.http_open(req).read()
        xml_file = minidom.parseString(response)
        self.set_values(xml_file)
        self.total_results = self.get_total_results(xml_file)
        for entry in xml_file.getElementsByTagName('entry'):
            self.albums.append(Album(entry, self.title))
        self.size = len(self.albums)
        self.expanded = True            
        
class PicasaWeb(Fuse):
    def __init__(self, *args, **kw):
        Fuse.__init__(self, *args, **kw)
        self.list_of_users = []

    def dir_from_list(self, list):
        """
        Return a properly formatted list of items suitable to a directory listing.
        ['a', 'b', 'c'] => [('a', 0), ('b', 0), ('c', 0)]
        """
        return [(str(x), 0) for x in list]

    def get_depth(self, path):
        if path == '/':
            return 0
        else:
            return path.count ('/')

    def get_parts(self, path):
        if path == '/':
            return ['/']
        else:
            return path.split('/')[1:]

    def get_user(self, user):
        newuser = Users(user)
        self.list_of_users.append(newuser)
        return newuser

    def find_user(self, username):
        for user in self.list_of_users:
            if user.title == username:
                return user
        print username, 'not already present.. generating now'
        return self.get_user(username)
        
    def getdir(self, path):
        depth = self.get_depth(path)
        if depth == 0:
            users = [user.title for user in self.list_of_users]
            return self.dir_from_list(users)
        elif depth == 1:
            parts = self.get_parts(path)
            user = self.find_user(parts[-1])
            if not user:
                # self.get_user(parts[-1])
                user = self.find_user(parts[-1])
                if not user:
                    return -errno.ENOSYS                
            albums = user.get_albums()
            return self.dir_from_list(albums)
        elif depth == 2:
            parts = self.get_parts(path)
            user = self.find_user(parts[-2])
            album = user.find_album(parts[-1])
            photos = album.get_photos()
            return self.dir_from_list(photos)

    def path_to_object(self, path):
        """Converts a file system path to the associated Object --
        User/Album/Photo
        """
        depth = self.get_depth(path)
        if depth == 0:
            return None
        parts = self.get_parts(path)
        user = self.find_user(parts[0])
        if depth == 1: return user
        album = user.find_album(parts[1])
        if depth == 2: return album
        return album.find_photo(parts[2])
        
    def read(self, path, length, offset):
        print 'Inside read', path, length, offset
        if self.get_depth(path) <= 2:
            return -errno.ENOSYS
        photo = self.path_to_object(path)
        if not photo:
            return -errno.ENOSYS
        return photo.read(length, offset)

    def getattr(self, path):
        """
        - st_mode (protection bits)
        - st_ino (inode number)
        - st_dev (device)
        - st_nlink (number of hard links)
        - st_uid (user ID of owner)
        - st_gid (group ID of owner)
        - st_size (size of file, in bytes)
        - st_atime (time of most recent access)
        - st_mtime (time of most recent content modification)
        - st_ctime (platform dependent; time of most recent metadata change on Unix,
                    or the time of creation on Windows).
        """
        print ' *** getattr', path
        depth = self.get_depth(path)
        mode = stat.S_IRWXU | stat.S_IRGRP | stat.S_IROTH | stat.S_IXGRP | stat.S_IXOTH
        size = 0
        upd = 0
        if depth > 2:
            mode |= stat.S_IFREG # regular file
        else:
            mode |= stat.S_IFDIR # define as directory
        obj = self.path_to_object(path)
        if obj:
            upd = obj.get_time()
            size = obj.find_size()
        elif path != '/':
            return -errno.ENOSYS
        return (mode, 0, 0, 0, 0, 0, long(size), upd, upd, upd)

    def mkdir(self, path, mode):
        return -errno.ENOSYS

    def rmdir(self, path):
        return -errno.ENOSYS

    def open(self, path, flags):
        # check for incorrect flags and raise exception here
        return

    def release(self, path, flags):
        return

    def unlink(self, path):
        return -errno.ENOSYS
    
if __name__ == '__main__':
    fs = PicasaWeb()
    fs.flags = 0
    fs.multithreaded = 0
    fs.main()
